package com.openswimsoftware.orm.model.list.query;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

import com.openswimsoftware.orm.ORM;
import com.openswimsoftware.orm.model.Model;
import com.openswimsoftware.orm.model.exception.InvalidModelField;
import com.openswimsoftware.orm.model.exception.InvalidModelRelationship;
import com.openswimsoftware.orm.model.list.ModelList;
import com.openswimsoftware.orm.model.query.order.OrderDirection;
import com.openswimsoftware.orm.model.query.order.QueryOrderBy;
import com.openswimsoftware.orm.model.query.where.QueryWhere;
import com.openswimsoftware.orm.model.query.where.WhereCombo;
import com.openswimsoftware.orm.model.query.where.WhereOperator;

import android.content.Context;
import android.database.Cursor;
import android.util.Log;

public class ModelListFind<E extends  Model<E>> extends ModelListQuery<E, ModelListFind<E>> {
	
	private QueryOrderBy orderBy;

	private long limit = -1;
	private boolean distinct = false;
	
	public ModelListFind(Context context, Class<E> modelType, ModelList<E> models)
	{
		super(context, modelType, models);
	}
	
	public ModelListFind<E> where(String field, WhereOperator operator, String value) throws InvalidModelField
	{
		this.where(WhereCombo.AND, field, operator, value);
		
		return this;
	}
	
	public ModelListFind<E> where(WhereCombo combo, String field, WhereOperator operator, String value) throws InvalidModelField
	{
		this.whereList.add(new QueryWhere(combo, ORM.getField(this.modelType, field), operator, value));
		
		return this;
	}
	
	public ModelListFind<E> order(String field) throws InvalidModelField
	{
		return this.order(field, OrderDirection.ASC);
	}
	
	public ModelListFind<E> order(String field, OrderDirection direction) throws InvalidModelField
	{
		this.orderBy = new QueryOrderBy(OrderDirection.ASC, new Field[]{ ORM.getDataField(this.modelType, field) });
		
		return this;
	}
	
	public ModelListFind<E> order(String[] fieldNames) throws InvalidModelField
	{
		return order(fieldNames, OrderDirection.ASC);
	}
	
	public ModelListFind<E> order(String[] fieldNames, OrderDirection direction) throws InvalidModelField
	{		
		Field[] fields = new Field[fieldNames.length];
		
		int index = 0;
		for (String fieldName: fieldNames)
		{
			fields[index++] = ORM.getDataField(this.modelType, fieldName);
		}
		
		this.orderBy = new QueryOrderBy(OrderDirection.ASC, fields);
		
		return this;
	}
	
	public ModelListFind<E> limit(int limit)
	{
		this.limit = limit;
		
		return this;
	}
	
	public ModelListFind<E> distinct(boolean distinct)
	{
		this.distinct = distinct;
		
		return this;
	}
	
	public ModelList<E> execute()
	{
		this.dbInterface.openDB();
		
		if (this.whereList.size() == 0)
		{
			try 
			{
				if (this.models.getParentId() > -1)
				{
					this.where(this.models.getParentName(), WhereOperator.EQUAL, this.models.getParentId()+"");
				}
			} 
			catch (InvalidModelField e) 
			{
				e.printStackTrace();
			}
		}
		
		String table = ORM.tableName(this.modelType);
		String where = QueryWhere.whereString(this.whereList);
		String[] whereArgs = QueryWhere.whereArgs(this.whereList);
		String orderBy = QueryOrderBy.orderByString(this.orderBy);
		
		Log.d("orm", "Find from list ModelList<" + this.modelType.getSimpleName() + ">");
		if (where != null)
		{
			Log.d("orm", "Where: " + where);
			for (String arg: whereArgs)
			{
				Log.d("orm", arg);
			}
		}
		if (orderBy != null)
		{
			Log.d("orm", "Order By: " + orderBy);
		}
		
		Cursor cursor;
		if (this.limit > -1)
		{
			cursor = this.dbInterface.getDB().query(this.distinct, table, null, where, whereArgs, null, null, orderBy, this.limit+"");
		}
		else
		{
			cursor = this.dbInterface.getDB().query(this.distinct, table, null, where, whereArgs, null, null, orderBy, null);
		}
		cursor.moveToFirst();
		
		while ( ! cursor.isAfterLast())
		{
			try 
			{
				
				Constructor<?> constructor = modelType.getConstructor(Context.class);
				constructor.setAccessible(true);
				
				@SuppressWarnings("unchecked")
				E model = (E) constructor.newInstance(this.context);
				
				model.fromCursor(cursor);
				model.setUpdated(false);
				model.setLoaded(true);
				
				this.models.add(model);
			} 
			catch (IllegalArgumentException e) 
			{
				e.printStackTrace();
			} 
			catch (IllegalAccessException e) 
			{
				e.printStackTrace();
			} 
			catch (NoSuchMethodException e) 
			{
				e.printStackTrace();
			} 
			catch (InstantiationException e) 
			{
				e.printStackTrace();
			} 
			catch (InvocationTargetException e) 
			{
				e.printStackTrace();
			} 
			catch (InvalidModelRelationship e) 
			{
				e.printStackTrace();
			}
			
			cursor.moveToNext();
		}
		cursor.close();
		
		this.models.setLoaded(true);
		
		this.dbInterface.closeDB();
		
		return this.models;
	}
}
